﻿using ProjScan.RecordManager;
using ProjScan.RecordManager.AssetData;
using ProjScan.ScanRule.Attr;
using ProjScan.Submodules.Art_Assets_Check;
using ProjScan.Tools;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Rendering;

namespace ProjScan.ScanRule.ArtAssets.Mesh
{
    [ArtAssetRuleAttr]
    internal class HasInvisibleFaces : RecordRuleImp<MeshRenderingAssetData, MeshRenderingRecordManager>
    {
        private sealed class ViewIterator : IDisposable, IEnumerable<Matrix4x4>, IEnumerable, IEnumerator<Matrix4x4>, IEnumerator
        {
            private int state;

            private Matrix4x4 current;

            private int initialThreadId;

            private RenderTexture renderTexture;

            public RenderTexture RenderTextureCopy;

            private Vector3 center;

            public Vector3 CenterCopy;

            private float size;

            public float SizeCopy;

            private UnityEngine.Mesh mesh;

            public UnityEngine.Mesh MeshCopy;

            private List<Vector3> positions;

            private int currentIndex;

            private Matrix4x4 projectionMatrix;

            private Matrix4x4 modelMatrix;

            private Matrix4x4 viewMatrix;

            private RenderTexture prevRt;

            private Matrix4x4 mvp;

            Matrix4x4 IEnumerator<Matrix4x4>.Current => current;

            object IEnumerator.Current => current;

            public ViewIterator(int state)
            {
                this.state = state;
                initialThreadId = Thread.CurrentThread.ManagedThreadId;
            }

            public bool MoveNext()
            {
                switch (state)
                {
                    default:
                        return false;
                    case 1:
                        state = -1;
                        projectionMatrix = default(Matrix4x4);
                        modelMatrix = default(Matrix4x4);
                        viewMatrix = default(Matrix4x4);
                        prevRt = null;
                        mvp = default(Matrix4x4);
                        currentIndex++;
                        break;
                    case 0:
                        state = -1;
                        positions = ShaderUtility.FetchVectors(1);
                        currentIndex = 0;
                        break;
                }

                if (currentIndex >= positions.Count)
                {
                    return false;
                }

                projectionMatrix = Matrix4x4.Ortho(0f - size, size, 0f - size, size, size / 2f, size * 3f);
                modelMatrix = Matrix4x4.TRS(-center, Quaternion.LookRotation(-positions[currentIndex], Vector3.up), Vector3.one);
                viewMatrix = Matrix4x4.TRS(new Vector3(0f, 0f, (0f - size) * 2f), Quaternion.LookRotation(Vector3.forward, Vector3.up), Vector3.one);
                Graphics.SetRenderTarget(renderTexture);
                prevRt = RenderTexture.active;
                RenderTexture.active = renderTexture;
                GL.PushMatrix();
                GL.LoadProjectionMatrix(projectionMatrix);
                GL.modelview = viewMatrix * modelMatrix;
                GL.invertCulling = true;
                GL.Clear(clearDepth: true, clearColor: true, Color.black);
                Graphics.DrawMeshNow(mesh, Matrix4x4.identity);
                GL.IssuePluginEvent(0);
                GL.PopMatrix();
                projectionMatrix = GL.GetGPUProjectionMatrix(projectionMatrix, renderIntoTexture: true);
                mvp = projectionMatrix * (viewMatrix * modelMatrix);
                GL.invertCulling = false;
                RenderTexture.active = prevRt;
                current = mvp;
                state = 1;
                return true;
            }

            IEnumerator<Matrix4x4> IEnumerable<Matrix4x4>.GetEnumerator()
            {
                ViewIterator viewIterator;
                if (state == -2 && initialThreadId == Thread.CurrentThread.ManagedThreadId)
                {
                    state = 0;
                    viewIterator = this;
                }
                else
                {
                    viewIterator = new ViewIterator(0);
                }

                viewIterator.renderTexture = RenderTextureCopy;
                viewIterator.center = CenterCopy;
                viewIterator.size = SizeCopy;
                viewIterator.mesh = MeshCopy;
                return viewIterator;
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable<Matrix4x4>)this).GetEnumerator();
            }

            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }

            void IDisposable.Dispose()
            {
            }
        }

        protected override bool UseAssetDataCSVHeader => false;

        public override void CheckDirSetted()
        {
            if (SeparateDirSettingsEnabled)
            {
                if (target_configs == null || target_configs.Length == 0)
                {
                    ScanLog.UnityLogError("美术资源检测规则 " + GetType().Name + " 执行失败，未设置目标文件夹");
                }

                if (target_configs.Length == 1 && target_configs[0].assetPath.StartsWith("Assets/ArtCheckFolder"))
                {
                    ScanLog.UnityLogError("美术资源检测规则 " + GetType().Name + " 执行失败，未设置目标文件夹");
                }
            }
        }

        protected override string GetDetailCSVHeader()
        {
            return "Name,DirID,InvisibleFaces";
        }

        public static int[] CheckInvisibleFaces(UnityEngine.Mesh mesh)
        {
            RenderTexture renderTexture = null;
            ComputeBuffer vertexBuffer = null;
            ComputeBuffer indexBuffer = null;
            ComputeBuffer visibilityBuffer = null;
            int[] visibility = null;
            InitializeRenderer(mesh, out int faceCount, out Vector3 meshCenter, out float meshSize, out renderTexture, out vertexBuffer, out indexBuffer, out visibilityBuffer, out visibility);
            foreach (Matrix4x4 item in GetView(renderTexture, meshCenter, meshSize, mesh))
            {
                ShaderUtility.LoadComputeShader().SetMatrix("v5", item);
                ShaderUtility.LoadComputeShader().SetTexture(ShaderUtility.FetchKernelK1(), "v11", renderTexture);
                ShaderUtility.LoadComputeShader().Dispatch(ShaderUtility.FetchKernelK1(), Mathf.CeilToInt((float)faceCount / 64f), 1, 1);
            }

            List<int> list = new List<int>();
            visibilityBuffer.GetData(visibility);
            for (int i = 0; i < visibility.Length; i++)
            {
                if (visibility[i] == 0)
                {
                    list.Add(i);
                }
            }

            vertexBuffer.Release();
            indexBuffer.Release();
            visibilityBuffer.Release();
            return list.ToArray();
        }

        public override bool CheckSupportability(out string msg)
        {
            if (!SystemInfo.supportsComputeShaders)
            {
                msg = "请将Standalone Windows平台的Graphics API设置成DX，有些Unity版本还需将Edit->Graphics Emulation设置为No Emulation。";
                return false;
            }

            if (Singleton<ScanInfo>.Instance.graphicsDeviceType == GraphicsDeviceType.Direct3D11 || Singleton<ScanInfo>.Instance.graphicsDeviceType == GraphicsDeviceType.Direct3D12)
            {
                msg = "请将Standalone Windows平台的Graphics API设置成DX，有些Unity版本还需将Edit->Graphics Emulation设置为No Emulation。";
                return true;
            }

            msg = "请将Standalone Windows平台的Graphics API设置成DX，有些Unity版本还需将Edit->Graphics Emulation设置为No Emulation。";
            return false;
        }

        public static IEnumerable<Matrix4x4> GetView(RenderTexture rt, Vector3 center, float size, UnityEngine.Mesh mesh)
        {
            ViewIterator viewIterator = new ViewIterator(-2);
            viewIterator.RenderTextureCopy = rt;
            viewIterator.CenterCopy = center;
            viewIterator.SizeCopy = size;
            viewIterator.MeshCopy = mesh;
            return viewIterator;
        }

        public static void InitializeRenderer(UnityEngine.Mesh mesh, out int faceCount, out Vector3 meshCenter, out float meshSize, out RenderTexture renderTexture, out ComputeBuffer vertexBuffer, out ComputeBuffer indexBuffer, out ComputeBuffer visibilityBuffer, out int[] visibility)
        {
            renderTexture = new RenderTexture(512, 512, 32, RenderTextureFormat.ARGBHalf, RenderTextureReadWrite.Linear);
            renderTexture.Create();
            Graphics.SetRenderTarget(renderTexture);
            new Material(Shader.Find("Hidden/GetDepth")).SetPass(0);
            Bounds bounds = mesh.bounds;
            meshCenter = bounds.center;
            float a = Mathf.Max(bounds.size.x, bounds.size.y);
            meshSize = Mathf.Max(a, bounds.size.z);
            int[] indices = mesh.GetIndices(0);
            faceCount = indices.Length / 3;
            vertexBuffer = new ComputeBuffer(mesh.vertices.Length, 12, ComputeBufferType.Structured);
            vertexBuffer.SetData(mesh.vertices);
            indexBuffer = new ComputeBuffer(indices.Length, 4, ComputeBufferType.Structured);
            indexBuffer.SetData(indices);
            visibility = new int[faceCount];
            visibilityBuffer = new ComputeBuffer(faceCount, 4, ComputeBufferType.Structured);
            visibilityBuffer.SetData(visibility);
            ShaderUtility.LoadComputeShader().SetBuffer(ShaderUtility.FetchKernelK1(), "v7", vertexBuffer);
            ShaderUtility.LoadComputeShader().SetBuffer(ShaderUtility.FetchKernelK1(), "v8", indexBuffer);
            ShaderUtility.LoadComputeShader().SetBuffer(ShaderUtility.FetchKernelK1(), "v13", visibilityBuffer);
            ShaderUtility.LoadComputeShader().SetInt("v9", 512);
            ShaderUtility.LoadComputeShader().SetFloat("v12", meshSize);
        }

        protected override bool IsOK(MeshRenderingAssetData assetData)
        {
            int[] array = null;
            try
            {
                array = CheckInvisibleFaces(assetData.asset);
            }
            catch (Exception exception_)
            {
                ScanLog.Instance.LogException(exception_, "HasInvisibleFaces.CheckInvisibleFaces", "");
                assetData.invisibleFaces = "FAILED";
                return true;
            }

            string[] array2 = new string[array.Length];
            for (int i = 0; i < array.Length; i++)
            {
                array2[i] = array[i].ToString();
            }

            if (array.Length != 0)
            {
                assetData.invisibleFaces = string.Join("*", array2);
                return false;
            }

            return true;
        }
    }
}